#include "misc/helper.hpp"

#include "misc/arch.hpp"
#include "misc/string_ext.hpp"
#include "rewine/MapIndicator.hpp"

#include <cerrno>
#include <cstring>

#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <unistd.h>

using std::shared_ptr;
using std::string;

static inline uintptr_t
round_down(uintptr_t addr, long mask)
{
    return (uintptr_t) (addr & ~(uintptr_t)mask);
}

static inline uintptr_t
round_up(uintptr_t size, long mask)
{
    return (uintptr_t) ((uintptr_t)(size + mask) & ~(uintptr_t)mask);
}

static inline size_t
round_size(void *addr, size_t size, long mask)
{
    // no need to round_up(size) for glibc mmap() will do it later
    return size + ((((uintptr_t)addr & mask) + mask) & ~(uintptr_t)mask);
}

namespace rewine
{

static void
fd_deleter(const int *pfd)
{
    close(*pfd);
    delete pfd;
}

std::shared_ptr<int>
auto_fd(int fd)
{
    auto p = new int(fd);
    return shared_ptr<int>(p, fd_deleter);
}

int
open_file_with_readonly(const string &filename)
{
    return ::open(filename.c_str(), O_RDONLY);
}

static void
MapIndicator_deleter(const MapIndicator *map)
{
    ::munmap(map->front(), map->size);
    delete map;
}

std::shared_ptr<MapIndicator>
auto_MapIndicator(const std::shared_ptr<int> &pfd, off_t offset, void *start, size_t len)
{
    auto p = new MapIndicator(pfd, start, offset, len);
    return std::shared_ptr<MapIndicator>(p, MapIndicator_deleter);
}

int
mmap_readonly_file(const shared_ptr<int>& pfd, std::shared_ptr<MapIndicator> &map)
{
    map.reset();
    errno = 0;
    struct stat fd_stat;
    if (::fstat(*pfd, &fd_stat) < 0) {
        return -1;
    }
    size_t size = fd_stat.st_size;
    void *addr = ::mmap(NULL, size, PROT_READ, MAP_SHARED, *pfd, 0);
    if (addr == (void *) -1) {
        return -2;
    }
    map = auto_MapIndicator(pfd, 0, addr, size);
    return 0;
}

int
mmap_anonymous(void *start, size_t size, bool bFixed, bool bExec, std::shared_ptr<MapIndicator> &map)
{
    map.reset();

    int prot = PROT_READ | PROT_WRITE;
    if (bExec) prot |= PROT_EXEC;

    int flags = MAP_PRIVATE | MAP_ANONYMOUS;
    if (bFixed) flags |= MAP_FIXED;

    auto addr = ::mmap(start, size, prot, flags, -1, 0);
    if (addr == (void *) -1) {
        return -1;
    }
    if (bFixed && addr != start) {
        ::munmap(start, size);
        return -2;
    }
    map = auto_MapIndicator(nullptr, 0, addr, size);
    return 0;
}

} // namespace rewine
